import React, { SyntheticEvent, useState } from 'react';
import semver from 'semver/preload';

import {
  DataSourcePluginOptionsEditorProps,
  DataSourceSettings as DataSourceSettingsType,
  onUpdateDatasourceJsonDataOptionChecked,
  SelectableValue,
  updateDatasourcePluginJsonDataOption,
} from '@grafana/data';
import { getBackendSrv } from '@grafana/runtime/src';
import { InlineField, Input, Select, Switch, useTheme2 } from '@grafana/ui';

import config from '../../../../core/config';
import { useUpdateDatasource } from '../../../../features/datasources/state';
import { PromApplication, PromBuildInfoResponse } from '../../../../types/unified-alerting-dto';
import { QueryEditorMode } from '../querybuilder/shared/types';
import { defaultPrometheusQueryOverlapWindow } from '../querycache/QueryCache';
import { PrometheusCacheLevel, PromOptions } from '../types';

import { docsTip, overhaulStyles, PROM_CONFIG_LABEL_WIDTH, validateInput } from './ConfigEditor';
import { ExemplarsSettings } from './ExemplarsSettings';
import { PromFlavorVersions } from './PromFlavorVersions';

const httpOptions = [
  { value: 'POST', label: 'POST' },
  { value: 'GET', label: 'GET' },
];

const editorOptions = [
  { value: QueryEditorMode.Builder, label: 'Builder' },
  { value: QueryEditorMode.Code, label: 'Code' },
];

const cacheValueOptions = [
  { value: PrometheusCacheLevel.Low, label: 'Low' },
  { value: PrometheusCacheLevel.Medium, label: 'Medium' },
  { value: PrometheusCacheLevel.High, label: 'High' },
  { value: PrometheusCacheLevel.None, label: 'None' },
];

type PrometheusSelectItemsType = Array<{ value: PromApplication; label: PromApplication }>;

const prometheusFlavorSelectItems: PrometheusSelectItemsType = [
  { value: PromApplication.Prometheus, label: PromApplication.Prometheus },
  { value: PromApplication.Cortex, label: PromApplication.Cortex },
  { value: PromApplication.Mimir, label: PromApplication.Mimir },
  { value: PromApplication.Thanos, label: PromApplication.Thanos },
];

type Props = Pick<DataSourcePluginOptionsEditorProps<PromOptions>, 'options' | 'onOptionsChange'>;

// single duration input
export const DURATION_REGEX = /^$|^\d+(ms|[Mwdhmsy])$/;

// multiple duration input
export const MULTIPLE_DURATION_REGEX = /(\d+)(.+)/;

const durationError = '值无效，可以将数字与时间单位说明符一起使用: y, M, w, d, h, m, s';
/**
 * Returns the closest version to what the user provided that we have in our PromFlavorVersions for the currently selected flavor
 * Bugs: It will only reject versions that are a major release apart, so Mimir 2.x might get selected for Prometheus 2.8 if the user selects an incorrect flavor
 * Advantages: We don't need to maintain a list of every possible version for each release
 *
 * This function will return the closest version from PromFlavorVersions that is equal or lower to the version argument,
 * unless the versions are a major release apart.
 */
const getVersionString = (version: string, flavor?: string): string | undefined => {
  if (!flavor || !PromFlavorVersions[flavor]) {
    return;
  }
  const flavorVersionValues = PromFlavorVersions[flavor];

  // As long as it's assured we're using versions which are sorted, we could just filter out the values greater than the target version, and then check the last element in the array
  const versionsLessThanOrEqual = flavorVersionValues
    ?.filter((el) => !!el.value && semver.lte(el.value, version))
    .map((el) => el.value);

  const closestVersion = versionsLessThanOrEqual[versionsLessThanOrEqual.length - 1];

  if (closestVersion) {
    const differenceBetweenActualAndClosest = semver.diff(closestVersion, version);

    // Only return versions if the target is close to the actual.
    if (['patch', 'prepatch', 'prerelease', null].includes(differenceBetweenActualAndClosest)) {
      return closestVersion;
    }
  }

  return;
};

const unableToDeterminePrometheusVersion = (error?: Error): void => {
  console.warn('从buildinfo API获取版本时出错，必须手动选择版本!', error);
};

/**
 * I don't like the daisy chain of network requests, and that we have to save on behalf of the user, but currently
 * the backend doesn't allow for the prometheus client url to be passed in from the frontend, so we currently need to save it
 * to the database before consumption.
 *
 * Since the prometheus version fields are below the url field, we can expect users to populate this field before
 * hitting save and test at the bottom of the page. For this case we need to save the current fields before calling the
 * resource to auto-detect the version.
 *
 * @param options
 * @param onOptionsChange
 * @param onUpdate
 */
const setPrometheusVersion = (
  options: DataSourceSettingsType<PromOptions>,
  onOptionsChange: (options: DataSourceSettingsType<PromOptions>) => void,
  onUpdate: (dataSource: DataSourceSettingsType<PromOptions>) => Promise<DataSourceSettingsType<PromOptions>>
) => {
  // This will save the current state of the form, as the url is needed for this API call to function
  onUpdate(options)
    .then((updatedOptions) => {
      getBackendSrv()
        .get(`/api/datasources/uid/${updatedOptions.uid}/resources/version-detect`)
        .then((rawResponse: PromBuildInfoResponse) => {
          const rawVersionStringFromApi = rawResponse.data?.version ?? '';
          if (rawVersionStringFromApi && semver.valid(rawVersionStringFromApi)) {
            const parsedVersion = getVersionString(rawVersionStringFromApi, updatedOptions.jsonData.prometheusType);
            // If we got a successful response, let's update the backend with the version right away if it's new
            if (parsedVersion) {
              onUpdate({
                ...updatedOptions,
                jsonData: {
                  ...updatedOptions.jsonData,
                  prometheusVersion: parsedVersion,
                },
              }).then((updatedUpdatedOptions) => {
                onOptionsChange(updatedUpdatedOptions);
              });
            }
          } else {
            unableToDeterminePrometheusVersion();
          }
        });
    })
    .catch((error) => {
      unableToDeterminePrometheusVersion(error);
    });
};

export const PromSettings = (props: Props) => {
  const { options, onOptionsChange } = props;

  // This update call is typed as void, but it returns a response which we need
  const onUpdate = useUpdateDatasource();

  // We are explicitly adding httpMethod so, it is correctly displayed in dropdown.
  // This way, it is more predictable for users.
  if (!options.jsonData.httpMethod) {
    options.jsonData.httpMethod = 'POST';
  }

  const theme = useTheme2();
  const styles = overhaulStyles(theme);

  type ValidDuration = {
    timeInterval: string;
    queryTimeout: string;
    incrementalQueryOverlapWindow: string;
  };

  const [validDuration, updateValidDuration] = useState<ValidDuration>({
    timeInterval: '',
    queryTimeout: '',
    incrementalQueryOverlapWindow: '',
  });

  return (
    <>
      <h3 className="page-heading">Interval behaviour</h3>
      <div className="gf-form-group">
        {/* Scrape interval */}
        <div className="gf-form-inline">
          <div className="gf-form">
            <InlineField
              label="Scrape interval"
              labelWidth={PROM_CONFIG_LABEL_WIDTH}
              tooltip={
                <>
                  这个间隔就是普罗米修斯刮伤目标的频率。将其设置为典型刮伤，然后
                  Prometheus配置文件中配置的评估间隔。如果将其设置为大于
                  您的Prometheus配置文件间隔，Grafana将根据此间隔评估数据
                  您将看到更少的数据点。默认为15秒. {docsTip()}
                </>
              }
              interactive={true}
              disabled={options.readOnly}
            >
              <>
                <Input
                  className="width-20"
                  value={options.jsonData.timeInterval}
                  spellCheck={false}
                  placeholder="15s"
                  onChange={onChangeHandler('timeInterval', options, onOptionsChange)}
                  onBlur={(e) => updateValidDuration({ ...validDuration, timeInterval: e.currentTarget.value })}
                />
                {validateInput(validDuration.timeInterval, DURATION_REGEX, durationError)}
              </>
            </InlineField>
          </div>
        </div>
        {/* Query Timeout */}
        <div className="gf-form-inline">
          <div className="gf-form">
            <InlineField
              label="Query timeout"
              labelWidth={PROM_CONFIG_LABEL_WIDTH}
              tooltip={<>设置普罗米修斯查询超时. {docsTip()}</>}
              interactive={true}
              disabled={options.readOnly}
            >
              <>
                <Input
                  className="width-20"
                  value={options.jsonData.queryTimeout}
                  onChange={onChangeHandler('queryTimeout', options, onOptionsChange)}
                  spellCheck={false}
                  placeholder="60s"
                  onBlur={(e) => updateValidDuration({ ...validDuration, queryTimeout: e.currentTarget.value })}
                />
                {validateInput(validDuration.queryTimeout, DURATION_REGEX, durationError)}
              </>
            </InlineField>
          </div>
        </div>
      </div>

      <h3 className="page-heading">Query editor</h3>
      <div className="gf-form-group">
        <div className="gf-form">
          <InlineField
            label="Default editor"
            labelWidth={PROM_CONFIG_LABEL_WIDTH}
            tooltip={<>为此数据源的所有用户设置默认编辑器选项. {docsTip()}</>}
            interactive={true}
            disabled={options.readOnly}
          >
            <Select
              aria-label={`Default Editor (Code or Builder)`}
              options={editorOptions}
              value={
                editorOptions.find((o) => o.value === options.jsonData.defaultEditor) ??
                editorOptions.find((o) => o.value === QueryEditorMode.Builder)
              }
              onChange={onChangeHandler('defaultEditor', options, onOptionsChange)}
              width={40}
            />
          </InlineField>
        </div>
        <div className="gf-form">
          <InlineField
            labelWidth={PROM_CONFIG_LABEL_WIDTH}
            label="Disable metrics lookup"
            tooltip={
              <>
                选中此选项将禁用查询字段中的度量选择器和度量/标签支持&apos;s
                自动完成。如果您在使用更大的普罗米修斯实例时遇到性能问题，这将有所帮助. {docsTip()}
              </>
            }
            interactive={true}
            disabled={options.readOnly}
            className={styles.switchField}
          >
            <Switch
              value={options.jsonData.disableMetricsLookup ?? false}
              onChange={onUpdateDatasourceJsonDataOptionChecked(props, 'disableMetricsLookup')}
            />
          </InlineField>
        </div>
      </div>

      <h3 className="page-heading">Performance</h3>
      {!options.jsonData.prometheusType && !options.jsonData.prometheusVersion && options.readOnly && (
        <div className={styles.versionMargin}>
          有关在数据源中配置prometheus类型和版本的更多信息，请参阅{' '}
          <a
            className={styles.textUnderline}
            href="https://grafana.com/docs/grafana/latest/administration/provisioning/"
          >
            供应文档
          </a>
          .
        </div>
      )}
      <div className="gf-form-group">
        <div className="gf-form-inline">
          <div className="gf-form">
            <InlineField
              label="Prometheus type"
              labelWidth={PROM_CONFIG_LABEL_WIDTH}
              tooltip={
                <>
                  将其设置为普罗米修斯数据库的类型，例如普罗米修斯、Cortex、Mimir或Thanos。正在更改
                  此字段将保存您当前的设置，并尝试检测版本。某些类型的
                  普罗米修斯支持或不支持各种API。例如，某些类型支持的regex匹配
                  为查询添加标签以提高性能。有些类型具有元数据的API。如果设置不正确
                  在查询度量和标签时，您可能会遇到奇怪的行为。请检查你的普罗米修斯
                  文档，以确保输入正确的类型. {docsTip()}
                </>
              }
              interactive={true}
              disabled={options.readOnly}
            >
              <Select
                aria-label="Prometheus type"
                options={prometheusFlavorSelectItems}
                value={prometheusFlavorSelectItems.find((o) => o.value === options.jsonData.prometheusType)}
                onChange={onChangeHandler(
                  'prometheusType',
                  {
                    ...options,
                    jsonData: { ...options.jsonData, prometheusVersion: undefined },
                  },
                  (options) => {
                    // Check buildinfo api and set default version if we can
                    setPrometheusVersion(options, onOptionsChange, onUpdate);
                    return onOptionsChange({
                      ...options,
                      jsonData: { ...options.jsonData, prometheusVersion: undefined },
                    });
                  }
                )}
                width={40}
              />
            </InlineField>
          </div>
        </div>
        <div className="gf-form-inline">
          {options.jsonData.prometheusType && (
            <div className="gf-form">
              <InlineField
                label={`${options.jsonData.prometheusType} version`}
                labelWidth={PROM_CONFIG_LABEL_WIDTH}
                tooltip={
                  <>
                    使用此设置 {options.jsonData.prometheusType} 实例（如果未自动配置）. {docsTip()}
                  </>
                }
                interactive={true}
                disabled={options.readOnly}
              >
                <Select
                  aria-label={`${options.jsonData.prometheusType} type`}
                  options={PromFlavorVersions[options.jsonData.prometheusType]}
                  value={PromFlavorVersions[options.jsonData.prometheusType]?.find(
                    (o) => o.value === options.jsonData.prometheusVersion
                  )}
                  onChange={onChangeHandler('prometheusVersion', options, onOptionsChange)}
                  width={40}
                />
              </InlineField>
            </div>
          )}
        </div>
        {config.featureToggles.prometheusResourceBrowserCache && (
          <div className="gf-form-inline">
            <div className="gf-form max-width-30">
              <InlineField
                label="Cache level"
                labelWidth={PROM_CONFIG_LABEL_WIDTH}
                tooltip={
                  <>
                    设置编辑器查询的浏览器缓存级别。建议对高基数数据源使用更高的缓存设置s.
                  </>
                }
                interactive={true}
                disabled={options.readOnly}
              >
                <Select
                  width={40}
                  onChange={onChangeHandler('cacheLevel', options, onOptionsChange)}
                  options={cacheValueOptions}
                  value={
                    cacheValueOptions.find((o) => o.value === options.jsonData.cacheLevel) ?? PrometheusCacheLevel.Low
                  }
                />
              </InlineField>
            </div>
          </div>
        )}

        <div className="gf-form-inline">
          <div className="gf-form max-width-30">
            <InlineField
              label="Incremental querying (beta)"
              labelWidth={PROM_CONFIG_LABEL_WIDTH}
              tooltip={
                <>
                  此功能将更改相对查询的默认行为，使其始终从
                  prometheus实例，而查询结果将被缓存，并且只请求新的记录。
                  启用此选项可减少数据库和网络负载.
                </>
              }
              interactive={true}
              className={styles.switchField}
              disabled={options.readOnly}
            >
              <Switch
                value={options.jsonData.incrementalQuerying ?? false}
                onChange={onUpdateDatasourceJsonDataOptionChecked(props, 'incrementalQuerying')}
              />
            </InlineField>
          </div>
        </div>

        <div className="gf-form-inline">
          {options.jsonData.incrementalQuerying && (
            <InlineField
              label="Query overlap window"
              labelWidth={PROM_CONFIG_LABEL_WIDTH}
              tooltip={
                <>
                  将持续时间设置为10米、120秒或0秒。默认为10分钟。此持续时间将添加到每个增量请求的持续时间中.
                </>
              }
              interactive={true}
              disabled={options.readOnly}
            >
              <>
                <Input
                  onBlur={(e) =>
                    updateValidDuration({ ...validDuration, incrementalQueryOverlapWindow: e.currentTarget.value })
                  }
                  className="width-25"
                  value={options.jsonData.incrementalQueryOverlapWindow ?? defaultPrometheusQueryOverlapWindow}
                  onChange={onChangeHandler('incrementalQueryOverlapWindow', options, onOptionsChange)}
                  spellCheck={false}
                />
                {validateInput(validDuration.incrementalQueryOverlapWindow, MULTIPLE_DURATION_REGEX, durationError)}
              </>
            </InlineField>
          )}
        </div>
      </div>

      <h3 className="page-heading">Other</h3>
      <div className="gf-form-group">
        <div className="gf-form-inline">
          <div className="gf-form max-width-30">
            <InlineField
              label="Custom query parameters"
              labelWidth={PROM_CONFIG_LABEL_WIDTH}
              tooltip={
                <>
                  将自定义参数添加到Prometheus查询URL中。例如超时、部分响应、重复数据消除或
                  最大资源解决方案。多个参数应与“&”连接在一起. {docsTip()}
                </>
              }
              interactive={true}
              disabled={options.readOnly}
            >
              <Input
                className="width-20"
                value={options.jsonData.customQueryParameters}
                onChange={onChangeHandler('customQueryParameters', options, onOptionsChange)}
                spellCheck={false}
                placeholder="Example: max_source_resolution=5m&timeout=10"
              />
            </InlineField>
          </div>
        </div>
        <div className="gf-form-inline">
          {/* HTTP Method */}
          <div className="gf-form">
            <InlineField
              labelWidth={PROM_CONFIG_LABEL_WIDTH}
              tooltip={
                <>
                  您可以使用POST或GET HTTP方法来查询Prometheus数据源。POST是
                  推荐的方法，因为它允许更大的查询。如果您有普罗米修斯版本，请将此更改为GET
                  2.1以上，或者POST请求在您的网络中受到限制. {docsTip()}
                </>
              }
              interactive={true}
              label="HTTP method"
              disabled={options.readOnly}
            >
              <Select
                width={40}
                aria-label="Select HTTP method"
                options={httpOptions}
                value={httpOptions.find((o) => o.value === options.jsonData.httpMethod)}
                onChange={onChangeHandler('httpMethod', options, onOptionsChange)}
              />
            </InlineField>
          </div>
        </div>
      </div>
      <ExemplarsSettings
        options={options.jsonData.exemplarTraceIdDestinations}
        onChange={(exemplarOptions) =>
          updateDatasourcePluginJsonDataOption(
            { onOptionsChange, options },
            'exemplarTraceIdDestinations',
            exemplarOptions
          )
        }
        disabled={options.readOnly}
      />
    </>
  );
};

export const getValueFromEventItem = (eventItem: SyntheticEvent<HTMLInputElement> | SelectableValue<string>) => {
  if (!eventItem) {
    return '';
  }

  if (eventItem.hasOwnProperty('currentTarget')) {
    return eventItem.currentTarget.value;
  }

  return (eventItem as SelectableValue<string>).value;
};

const onChangeHandler =
  (key: keyof PromOptions, options: Props['options'], onOptionsChange: Props['onOptionsChange']) =>
  (eventItem: SyntheticEvent<HTMLInputElement> | SelectableValue<string>) => {
    onOptionsChange({
      ...options,
      jsonData: {
        ...options.jsonData,
        [key]: getValueFromEventItem(eventItem),
      },
    });
  };
